home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Programmer Disk
/
The Programmer Disk (Microforum).iso
/
xpro
/
c3
/
pro24
/
aintr.asm
< prev
next >
Wrap
Assembly Source File
|
1986-08-08
|
15KB
|
483 lines
;*****************************************************************************
; Change Log
; Date | Change
;-----------+-----------------------------------------------------------------
; 31-Dec-85 | Created change log
; 31-Dec-85 | Make sure DS: register is set properly!
; | Note: Why the CLD at the start of the routine? CLI?
; 1-Jan-86 | Removed CLD. Roger suggests this was carryover from 6502 code
; | where CLD is clear-decimal-mode.
; | Change 62H EOI code to 20H EOI code like everything else that
; | talks to interrupt chip. Note that we are tweaking the primary
; | interrupt controller chip on a /AT, but that is OK because the
; | EOI was sent to the secondary controller via the RE_DIRECT code
; | (PC/AT Tech Ref page 5-71)
; | This is the same mechanism used on both the /XT and /AT, e.g.
; | PC/XT Tech Ref page A-80, lines 5729-5730
; 5-Feb-86 | Keep interrupts off during interrupt handler
; 8-Feb-86 | Added code to capture system exclusive messages
; | Removed some debugging stores into d0,d1,d2,d3
; | Removed interrupt nesting counter -- if interrupts nest, you'll
; | crash before you can print the error report
; 13-Feb-86 | Changed DATA macro to a more sane name: GETMIDI
; 14-Feb-86 | Moved all variables to DSEG (they were in PSEG -- why did this
; | ever work before?)
; 5-May-86 | Optimized input for better transcription speed
; 9-Jul-86 | Added loop to avoid exiting interrupts with more data available
; 18-Jul-86 | Fixed a running status bug and cleaned up some debugging code
;*****************************************************************************
;;
;; MPU-401 interrupt handler
;; modelled after MPU-401 manual, pages 55-56
;; except that Ack commands are handled by setting
;; a flag and other commands are handled by
;; putting data into a buffer. Once things are
;; initialized, this is the only place that should
;; read data from the MPU-401. All writes (commands)
;; are issued from C routines.
;;
;; Notes: (Joe Newcomer, 31-Dec-85)
;; Because an interrupt can occur from anywhere, including DOS and
;; the BIOS, we cannot, repeat CANNOT assume the validity of any
;; register except CS:. In particular, SS:SP is quite possibly a
;; BIOS stack segment which are infinitesmally small. We CANNOT
;; push anything onto the BIOS stack segment without risking severe
;; damage to the integrity of the system. So we have here a large
;; private stack segement; we switch attention to it, *very carefully*
;; save our state on it, and then call the code which handles our
;; MPU-401 interrupt. Upon return, we *very carefully* reset the stack
;; and return to our caller. Since we need to address the C data segment,
;; we must also load DS:, which we need to set intnest and various buffer
;; headers. See the note associated with the setting of DS:; this
;; code works only in the small data model.
include dos.mac
; DEBUG = 1 ;; define DEBUG to enable some extra record keeping
DSEG
extrn interror:word ;; report errors up to C handlers
extrn timeerr:word ;; reports timeout errors to C handlers
extrn time_req:word ;; set to true if next Ack will be timing byte
IFDEF DEBUG
extrn loop_cnt:word ;; count loop interations
extrn loop_max:word ;; max value of loop interations
extrn intcnt:word ;; count of interrupts taken
ENDIF
extrn buff:byte ;; data from mpu401
extrn buffhead:word ;; data is removed from head offset
extrn bufftail:word ;; data is inserted at the tail offset
extrn xbuff:word ;; system exclusive buffer pointer
extrn xbuffhea:word
extrn xbufftai:word
extrn xbuffmas:word
;;
;; Globals used in communication with mpu.c
;;
extrn Ack:word ;; set if ack received
extrn Unknown:word ;; set for unknown command (for debugging)
extrn Ticks:dword ;; Clock ticks (400 = 1 second)
;;Midi information
extrn MidiTime:byte ;; extra timing byte
extrn MidiStat:byte ;; Running status
extrn Midi1:byte ;; First arg
extrn Midi2:byte ;; Second arg
extrn Midi3:byte ;; Third arg (not used)
extrn rd_delay:word ;; counts down wait for mpu data
ENDDS
pseg
public a_intr, init_asm
; These must be in the pseg because on entry only the CS: is addressible
DASEG DW 0
OldAX DW ?
OldSS DW ? ; old stack segment
OldSP DW ? ; old stack pointer
DW 512 DUP(?) ; local stack space for intercept routine
STACK label WORD
NESTERR = 1 ;;nested interrupt error
BUFFERR = 2 ;;input buffer overflow error
CMDERR = 3 ;;unknown command
TIMEOUT = 4 ;;timeout waiting to read data
BUFFMASK = 3FFH ;; buffer size is 1024 bytes, 3FF=1023
;; Status byte masks
;;
DRR = 40h ;; Data Receive Ready
DSR = 80h ;; Data Send Ready
STATPORT = 331H ;; MPU-401 Status port
DATAPORT = 330H ;; MPU-401 Data (MPU to PC) port
;*****************************************************************************
; init_asm(): called to save the data segment into a place where
; the interupt routine can get at it.
init_asm proc near
push bp ;save bp
mov bp,sp ;move sp into bp
mov cs:daseg,ds ;save the ds in daseg
pop bp
ret
init_asm endp
;*****************************************************************************
; a_intr
; Called via:
; far call from interrupt handler. NOTE: proc is declared 'near' so
; that funny fixups are not required when linking it into C small model
; code. Since we return via IRET, the near/far distinction does not
; matter. HOWEVER if one were to play funny games with doing returns
; and twiddling flags (unlikely) the near/far distinction would matter
;*****************************************************************************
a_intr proc near
; Establish a stack for us
mov OldSS,SS ; save old stack
mov OldSP,SP ; ...
mov OldAX,AX ; and scratch register
cli ; don't play with fire, turn 'em off
mov AX,CS ; our new stack segment is addressible by CS:
mov SS,AX ; ..
mov SP,offset STACK ; always change SS,SP in adjacent instructions
; In principle, we didn't need to turn
; interrupts off because doing it in that
; order guarantees that no interrupt will
; occur between mov SS and mov SP, but early
; 8088s had a bug and it didn't work.
; Better safe than sorry. An /XT could be
; repaired with one of these bogus chips
; sti ; allow interrupts again
; Save state
push ds ; save state
push es
push ax
push bx
push cx
push dx
push di
push si
push ds
; begin body
; Restore DS from value saved in pgroup:daseg.
mov bx,offset pgroup:daseg
mov ds,cs:[bx] ; now DS has the offset of dgroup segment
assume ds:dgroup
; mov ax,DGROUP
; mov ax,SEG intnest ; make DS be correct
; note: All variables have the same DS
; so doing it for one will do it for
; all
; This trick will not work in the large
; memory model; there we have to load
; DS: for each variable, because they
; could be in different segments
; No, I don't know how to handle the
; case where a long vector falls across
; a segment boundary
; at this point we may now validly address data
IFDEF DEBUG
inc intcnt ; up interrupt count
mov loop_cnt, 0 ; initialize iteration counter
ENDIF
readit: call mpu_aintr
; end body
IFDEF DEBUG
inc loop_cnt
ENDIF
mov al,20h ;;; EOI code
out 20h,al ;;; Announce end of interrupt
mov dx,STATPORT ;; load port number
in al,dx ;; read in char from port
test al,DSR ;
jz readit ;loop to handle next data byte
;; See note about the fact that we are
;; twiddling the primary interrupt controller
;; chip on an /AT, but this is no different
;; than what is required on the /XT
IFDEF DEBUG
mov ax, loop_cnt ;; loop_max = max(loop_max, loop_cnt)
cmp ax, loop_max
jb leave
mov loop_max, ax
ENDIF
leave: pop ds
pop si
pop di
pop dx
pop cx
pop bx
pop ax
pop es
pop ds
; Now restore our old stack
cli ; do it safely...
mov SS,OldSS ; restore SS
mov SP,OldSP ; restore SP
; sti ; allow them again
mov AX,OldAX ; restore AX
iret
a_intr endp
;;
;;
;; Data from mpu-401
;;
MPU_ACK = 0feh ;; acknowledgment of end of command
ABOVE_TIMING_BYTE = 0f0h ;; 1st value greater than legal timing
;; byte values (0 - 0efh)
TIMER_OVERFLOW = 0f8h ;; record timer reached 240
TIMER_INCR = 240d ;; add when TIMER_OVERFLOW comes
SYSTEM_MESSAGE = 0ffh ;; MIDI system message
MIDI_EXCLUSIVE = 0f0h ;; MIDI exclusive message
MIDI_EOX = 0f7h ;; MIDI EOX (end of MIDI exclusive)
MPUNOOP = 0f8h ;; MPU Mark: No Operation
;;
;; midi codes
;; high order 4 bits (of 8) give command
;; low order 4 bits give midi channel number
;;
MCOMMASK = 0f0h ;; These bits give MIDI command
MSTATUSMASK = 080h ;; This bit set if MIDI status byte
MCHANMASK = 00fh ;; These bits give MIDI channel number
NOTEOFF = 080h ;; status,pitch,veloc
NOTEON = 090h ;; status,pitch,veloc (=0 means off)
NOTEAFTERTOUCH = 0a0h ;; status,pitch,arg2
CONTROLCHANGE = 0b0h ;; status,arg1,arg2
PROGRAMCHANGE = 0c0h ;; status,program
CHAFTERTOUCH = 0d0h ;; status,arg
PITCHWHEEL = 0e0h ;; status,arg1,arg2
MPUCOM = 0f0h ;; fake midi command, really mpu401
MAXDELAY = 20000 ;; mpu_get times out after this many tries
;*****************************************************************************
; mpu_get
;
;*****************************************************************************
mpu_get proc near ;; read data from mpu 401
mov rd_delay,MAXDELAY
tryagain:
mov dx,STATPORT ;; read status port
in al,dx
test al,DSR ;; data ready to send?
jz gotit ;; yes - read the data
dec rd_delay ;; no - test for timeout
jnz tryagain ;; timed out? no - repeat
mov timeerr,TIMEOUT ;; yes - report error,
mov al,0f8h ;; and return innocuous (I hope) data
ret
gotit: mov dx,DATAPORT ;; load port number
in al,dx ;; read in char from port
ret
mpu_get endp
;*****************************************************************************
; putbuf
;*****************************************************************************
putbuf proc near ;; put data into buffer
mov dx,bufftail
add dx,4
and dx,BUFFMASK ;; wrap around ( dx = dx mod buffersize )
cmp dx,buffhead
je bufferfull
;; save new bufftail in dx, copy bytes
mov si,bufftail
mov bl, MidiStat
mov byte ptr buff[si],bl
inc si
mov bl, Midi1
mov byte ptr buff[si],bl
inc si
mov bl,Midi2
mov byte ptr buff[si],bl
mov bufftail,dx
ret
bufferfull:
mov interror,BUFFERR
ret
putbuf endp
GETMIDI macro ;; read the mpu 401 data port into al
call mpu_get
endm
;*****************************************************************************
; mpu_aintr
;*****************************************************************************
mpu_aintr proc near
GETMIDI 1,gm1 ;; get what 401 want us to get
mov ah,0 ;; several places assume ax = al
cmp ax,ABOVE_TIMING_BYTE ;; Timing byte?
jb l_timing_byte ;; (usually followed by midi data)
cmp al, TIMER_OVERFLOW
je l_timer_overflow
cmp al,MPU_ACK ;; Ack?
je l_mpu_ack
cmp al,SYSTEM_MESSAGE ;; Midi system message?
jne bad
jmp l_system_message
;;
;; This routine does not handle:
;; Track data requests
;; Conductor requests
;; Clock to host
;; musicinit() initializes the MPU-401 in such a way so that these bytes
;; are never sent. If they do appear, they end up here.
;;
bad:
mov Unknown,ax
mov interror,CMDERR
jmp bye
;;
;; Handle each class of 401 message
;;
;; An ack, set Ack so that mpu_wait() can see it.
l_mpu_ack:
inc Ack
cmp time_req, 0 ;; Does this command return timing data?
je ack_done ;; if not, just return
GETMIDI 2,gm2 ;; otherwise, read one more byte
mov ah, 0 ;; increment Ticks by result
add WORD PTR Ticks, ax
adc WORD PTR Ticks+2, 0
mov time_req, 0
ack_done:
jmp bye
;; A timer overflow, increment clock by appropriate number of ticks
l_timer_overflow:
add WORD PTR Ticks,TIMER_INCR ;; yes, do 32 bit incr of clock
adc WORD PTR Ticks+2,0
jmp bye
;; A timing byte - the hard case
;; There are a number of possibilities, on which we branch
l_timing_byte:
mov MidiTime,al ;; save timing byte
add WORD PTR Ticks,ax ;; yes, do 32 bit incr of clock
adc WORD PTR Ticks+2,0
GETMIDI 3,gm3 ;; get next byte
test al,MSTATUSMASK ;; It's midi, is it a status byte?
je runstat
;; Here we have new midi status byte. Stash it and read in first data
mov MidiStat,al
mov bl,al ;; copy command to bl
and bl,MCOMMASK ;; "And" off channel bits
cmp bl,MPUCOM ;; Is it an MPU command in disguise?
je l_mpucom ;; Yes, deal with it.
GETMIDI 4,gm4 ;; read in first data byte
jmp decode ;; decide whether 1 or 2 data bytes
runstat:
mov bl,MidiStat ;; no, use previous (running) status
and bl,MCOMMASK
;; Commands 0c0h (program change) and 0d0h (channel after touch) have 2 bytes
;; at this point, al has 1st data byte, bl has upper four bits of status byte
decode: mov Midi1,al ;; save first data byte
cmp bl,CHAFTERTOUCH
je gotmsg
cmp bl,PROGRAMCHANGE
je gotmsg
GETMIDI 5,gm5 ;; read second data byte
mov Midi2,al ;; save second data bytes
gotmsg:
;;
;; Here the midi command is contained in the (2 or) 3 bytes
;; MidiStat, Midi1, and Midi2
;;
call putbuf ;; put the data in the buffer
;; optimization note: only one call to putbuf
gobye: jmp bye
;;
;; MPU-401 marks
;; These shouldn't happen and are ignored. The NOOP mark IS sent
;; when recording, contrary to the MPU401 manual. Since it seems
;; harmless, no error is reported if a NOOP is sent. Otherwise,
;; report a bad command with the timing byte in the high-order byte
;; of the error data (Unknown) to distinguish the data as mark data.
;;
l_mpucom:
cmp al,MPUNOOP
je gobye ;; MPU-401 manual is wrong! The
mov ah,MidiTime ;; report two bytes as unknown
jmp bad
;; A MIDI system message, currently only read sys. exclusive messages
l_system_message:
;; see what the message is
GETMIDI 6,gm6
cmp al,MIDI_EXCLUSIVE
je store_x
jmp bad ;; only handle MIDI_EXCLUSIVE
store_x: ;; put data in buffer until MIDI_EOX read
mov bx,xbuff ;; do not store if xbuff is NULL
cmp bx,0
je nobuff
add bx,xbufftai ;; add index
mov byte ptr [bx],al ;; and store midi data
mov dx,xbufftai ;; increment with wrap-around
add dx,1
and dx,xbuffmas
mov xbufftai,dx
nobuff: test al,MSTATUSMASK ;; are we done?
je ex_continue ;; stop on any status byte ...
cmp al,MIDI_EXCLUSIVE ;; ... except midi exclusive
jne bye
ex_continue:
GETMIDI 7,gm7
jmp store_x
;; common return point
bye: ret
mpu_aintr endp
endps
end